package us.versus.them.b3ge.world;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.geom.Point;

import hxjson2.JSON;

import us.versus.them.b3ge.Util;
import us.versus.them.b3ge.character.Character;

/**
 *
 * A lot of this is ported from my "tw" project
 *
 */
class IsometricWorld extends Bitmap {

	var obj_height_        : Int;
	var floor_starts_      : Point;
	var map_width_         : Int;
	var map_height_        : Int;
	var tile_width_        : Int;
	var tile_height_       : Int;
	var tiles_             : Array< Array< Point > >;
	var map_               : Array< Array< Int > >;
	var tile_file_         : String;
	var offsets_           : Array< Point >;
	var tileBitmapData_    : BitmapData;
	var characters_        : Array< Character >;
	var iOffset_           : Int;
	var jOffset_           : Int;

	var w2:Int;
	var h2:Int;
	var x_start:Int;

	public function new() {
		super();
		this.characters_ = new Array< Character >();
		this.iOffset_ = this.jOffset_ = 0;
	}

	public function addCharacter( character:Character ) {
		this.characters_.push( character );
	}

	public function getTileBitmapData() {
		return this.tileBitmapData_;
	}

	public function setMapWidth( map_width ) {
		this.map_width_ = map_width;
	}

	public function getMapWidth() {
		return this.map_width_;
	}

	public function setMapHeight( map_height ) {
		this.map_height_ = map_height;
	}

	public function getMapHeight() {
		return this.map_height_;
	}

	public function setTileFile( tile_file ) {
		this.tile_file_ = tile_file;
	}

	public function getTileFile() {
		return this.tile_file_;
	}

	public function setTileWidth( tile_width ) {
		this.tile_width_ = tile_width;
	}

	public function getTileWidth() {
		return this.tile_width_;
	}

	public function setTileHeight( tile_height ) {
		this.tile_height_ = tile_height;
	}

	public function getTileHeight() {
		return this.tile_height_;
	}

	public function getObjectHeight() {
		return this.obj_height_;
	}

	public function setTiles( tiles ) {
		this.tiles_ = tiles;
	}

	public function getTiles() {
		return this.tiles_;
	}

	public function indexOk( i, j ) {
		return (
			i >= 0 && j >= 0 && i < this.getMapHeight() && j < this.getMapWidth() 
		);
	}

	public function offsetOk( idx ) {
		return idx >= 0 && idx < this.offsets_.length;
	}

	public function mapAtTo( i:Int, j:Int, idx:Int ) {
		if ( i >= 0 && i <= this.map_.length ) {
			var row = this.map_[ i ];
			if ( j >= 0 && j <= row.length ) {
				// TODO: check idx v/s this.offsets_.length?
				row[ j ] = idx;
			}
		}
	}

	public function tileAt( i:Int, j:Int ) {
		return if ( this.indexOk( i, j ) ) {
			this.getTiles()[ i ][ j ];
		} else {
			null;
		}
	}

	// this is in (j,i) order...
	public function tileIndex( event:MouseEvent ) {
		return this.tileIndex_(
			  event.stageX - this.x
			, event.stageY - this.y
		);
	}

	// this is in (j,i) order...
	public function tileIndex_( x:Float,y:Float ) {
		var at = this.clickedAt( x, y, false );

		// from http://allefant.sourceforge.net/isometricprojection/ 
		var tile_x = ( at.x / this.w2 + at.y / this.h2 ) / 2;
		var tile_y = ( at.y / this.h2 - at.x / this.w2 ) / 2;

		return new Point( 
			  tile_x + this.jOffset_
			, tile_y + this.iOffset_
		);
	}

	public function clickedAt( x:Float,y:Float, absolute=true ) : Point {
		x -= this.x_start;
		y -= this.obj_height_;
		if ( absolute ) {
			x += this.jOffset_ * this.w2;// + x_start;
			y += this.iOffset_ * this.h2;
		}
		return new Point( x, y );
	}

	public function fart() {
		return this.jOffset_ + ' , ' + this.iOffset_;
	}

	public function mapToScreen( p:Point ) { 
		var w = Math.floor( this.getTileWidth()  / 2 );
		var xstart = this.getMapWidth() * w - w;
		var mapped = new Point(
			p.x - p.y + xstart
			, ( p.x + p.y ) / 2
		);
		return mapped;
	}

	public function screenToWorld( p:Point ) {
		var x = p.x;
		var y = p.y;

		var w = Math.floor( this.getTileWidth() / 2 );
		var xstart = this.getMapWidth() * w - w;

		x -= xstart;
		var ymouse = ( ( 2 * y - x  - w ) / 2 );
		var xmouse = ( x + ymouse );
		var pt = new Point( ymouse, xmouse );
		return pt;
	}

	////

	public function setIOffset( i:Int ) {
		this.iOffset_ = i;
	}

	public function setJOffset( j:Int ) {
		this.jOffset_ = j;
	}

	public function supdate( i:Int,j:Int ) {
		this.setIOffset( i );
		this.setJOffset( j );
		this.update();
	}

	public var kookoo:String;

	public function update() {
this.kookoo = '';
		// thought this would clear the bitmpap, but doesn't seem to
		this.bitmapData.fillRect( 
			new Rectangle( 0, 0, this.bitmapData.width, this.bitmapData.height )
			, 0x0 
		);

		var fu = new Hash< Array< Dynamic > >();
		var offTile = new Point(
			  this.jOffset_ * this.w2 - this.iOffset_ * this.w2 
			, this.jOffset_ * this.h2 + this.iOffset_ * this.h2
		);

//this.kookoo += 'off:' + offTile ;
	
		for ( character in this.characters_ ) {
this.kookoo += ' @:' + character.position;
			character.x = ( character.position.x - character.position.y ) + this.x_start;
			character.y = ( character.position.x + character.position.y ) / 2 + this.obj_height_;

			var which = this.tileIndex_( character.x, character.y );

			character.x -= ( offTile.x + character.feet.x );
			character.y -= ( offTile.y + character.feet.y );

//this.kookoo += character.width + ',' + character.height + '...';

//this.kookoo += which + ',@:' + character.x + ',' + character.y;

			var i = Std.int( which.y );
			var j = Std.int( which.x );
//this.kookoo += ',off:' + character.x + ',' + character.y + ' to ' + i + ',' + j;

			var xo = character.x - j * this.w2;
			var yo = character.y - i * this.w2;
			var list = fu.get( i + '.' + j );
			if ( null == list ) {
				list = [];
				fu.set( i + '.' + j, list );
			}
			list.push( { xo:xo, yo:yo, character:character } );
		}

		for ( i in 0 ... this.getMapHeight() ) {
			var iRelative = i + this.iOffset_;

			if ( iRelative < 0 || iRelative >= this.map_.length ) {
				// TODO: clear additional rows...
				continue;
			}

			var row = this.map_[ iRelative ];

			for ( j in 0 ... this.getMapHeight() ) {
				var jRelative = j + this.jOffset_;
				if ( jRelative < 0 || jRelative >= row.length ) {
					// TODO: clear additional cells
				} else {
					this.copyPixelsFromOffsetAt( this.tiles_[ i ][ j ] , row[ jRelative ] );
				}

				var list = fu.get( i + '.' + j );
				if ( null != list ) {
					for ( gu in list ) {
						this.bitmapData.copyPixels(
							gu.character.actions.bitmapData
							, new Rectangle( 0, 0, gu.character.actions.width, gu.character.actions.height ) // so many new Rectangles!!!!
							, new Point( gu.character.x, gu.character.y )
			, null
			, new Point(0,0)
			, true // alpha, please! ok, thx! but only if you want!
						);

/*
var src:BitmapData = new BitmapData(100, 100, false, 0xFF000000);
var dst:BitmapData = new BitmapData(100, 100, false, 0xFFFFFFFF);
var srcXfrm:ColorTransform = new ColorTransform();
srcXfrm.alphaMultiplier = 1.0;
var dstXfrm:ColorTransform = new ColorTransform();
dstXfrm.alphaMultiplier = 0;
var rect:Rectangle = new Rectangle(0, 0, 100, 100);
while (xfrm.alphaMultiplier > 0) {  srcXfrm.alphaMultiplier -= 0.1;
  dstXfrm.alphaMultiplier += 0.1;
  dst.draw(..., dstXfrm, BlendMode.NORMAL, ...);
  src.draw(..., srcXfrm, BlendMode.ADD, ...);
}
*/
//actionsfunction copyPixels(sourceBitmapData : BitmapData, sourceRect : flash.geom.Rectangle, destPoint : flash.geom.Point, ?alphaBitmapData : BitmapData, ?alphaPoint : flash.geom.Point, ?mergeAlpha : Bool) : Void;

/*
						this.bitmapData.draw( 
							gu.character
							, null 
							, null
							, flash.display.BlendMode.ALPHA
							);
*/						
						//function draw(source : IBitmapDrawable, ?matrix : flash.geom.Matrix, ?colorTransform : flash.geom.ColorTransform, ?blendMode : BlendMode, ?clipRect : flash.geom.Rectangle, ?smoothing : Bool) : Void;
					}

				}

			}
		}
	}

	private function makeTiles() {
		this.w2 = Math.floor( this.getTileWidth()  / 2 );
		this.h2 = Math.floor( ( this.getTileHeight() - this.getObjectHeight() ) / 2 );
		this.x_start = this.getMapWidth() * this.w2 - this.w2;

		this.tiles_ = new Array< Array< Point > >();

		this.setSize( 
			  this.getTileWidth() * this.getMapWidth()
			, this.getTileHeight() * this.getMapHeight() 
		);

		for ( i in 0 ... this.getMapHeight() ) {
			var row = new Array< Point >();
			this.tiles_.push( row );
			for ( j in 0 ... this.getMapWidth() ) {
				var tile = this.newTile();
				tile.x = j * this.w2 - i * this.w2 + this.x_start;
				tile.y = j * this.h2 + i * this.h2;
				row.push( tile );
			}
		}
		this.update();
	}

	private function setSize( w, h ) {
		this.bitmapData = new BitmapData( w, h, true, 0x0 );
		this.width = w;
		this.height = h;
	}

	public function newTile() {
		return new Point();
	}

	public function copyPixelsToBitmap( tile:BitmapData, idx:Int ) {
		var offset = this.offsets_[ idx ];
		tile.copyPixels(
			this.tileBitmapData_
			, new Rectangle( offset.x, offset.y, this.tile_width_, this.tile_height_ )
			, new Point( 0, 0 )
			, null
			, null
			, true // alpha, please! ok, thx!
		);
	}

	public function copyPixelsFromOffsetAt( tile:Point, idx:Int ) {
		this.copyPixels( tile, this.offsets_[ idx ] );
	}

	public function copyPixels( tile:Point, offset:Point ) {
		this.bitmapData.copyPixels(
			this.tileBitmapData_
			, new Rectangle( offset.x, offset.y, this.tile_width_, this.tile_height_ )
			, tile
			, null
			, null
			, true // alpha, please! ok, thx!
		);
	}

	private function cut() {
		var idx = 0;
		var y = 0;
		this.offsets_ = new Array< Point >();
		while ( y < this.tileBitmapData_.height ) {
			var x = 0;
			while( x < this.tileBitmapData_.width ) {
				var pt = new Point( x, y );
				this.offsets_.push( pt );
				x += this.tile_width_;
			}
			y += this.tile_height_;
		}
	}

	public function init( config:Dynamic ) {
		this.tile_file_ = config.tile_file;
		this.tileBitmapData_ = Util.bitmapDataForName( this.tile_file_ );

		this.tile_width_ = config.tile_width;
		this.tile_height_ = config.tile_height;
		this.obj_height_ = config.obj_height;
		this.floor_starts_ = new Point( 0, this.obj_height_ - this.tile_height_ / 2 );

		this.cut();

		this.map_width_ = config.map_width;
		this.map_height_ = config.map_height;

		var a: Array<Array<Int>> = config.map;
		this.map_ = a;
		this.makeTiles();
	}

	public function parse( json:String ) {
		this.init( JSON.decode( json ) );
	}

	public override function toString() {
		var thiz = this;
		return JSON.encode(
			{
				  version     : "0.1"
				, tile_file   : thiz.tile_file_
				, obj_height  : thiz.obj_height_
				, tile_width  : thiz.tile_width_
				, tile_height : thiz.tile_height_
				, map_width   : thiz.map_width_
				, map_height  : thiz.map_height_
				, map         : thiz.map_
			}
		);
	}
}
